/****************************************************************************
**
** This file is part of qPhotoTag, a photo keywording application
**
** Copyright (C) 2009 Marcell Lengyel <miketkf@gmail.com>
**
** GNU General Public License
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**
****************************************************************************/

// C ANSI includes.
extern "C"
{
#include <sys/stat.h>
#include <utime.h>
}


#include <QtDebug>
#include "photo.h"



Photo::Photo(const QString &filename)
{
	save_needed = false;
	fullpath = filename;
	image = Exiv2::ImageFactory::open(filename.toStdString());
	image->readMetadata();
	exif_data = image->exifData();
	iptc_data = image->iptcData();
	// thumbnail
	Exiv2::ExifThumb thumbnail(exif_data);
	Exiv2::DataBuf data_buffer = thumbnail.copy();
	char *char_data = (char *)data_buffer.pData_;
	long len = data_buffer.size_;
	std::string data(len, ' ');
	for (long i = 0; i < len; ++i)
	{
		data[i] = char_data[i];
	}
	// thumbnail should be in data
	tn_data = data;
	tn_len = len;
	// caption
	caption = getIptcTagString("Iptc.Application2.Caption", true);
}

void Photo::save_all()
{
    if (! save_needed)
        return;
    // check if file is read only
    QFileInfo finfo(fullpath);
    QFileInfo dinfo(finfo.path());
    if (!finfo.isWritable())
        qDebug() << "File '" << finfo.fileName().toAscii().constData() << "' is read-only. Metadata not saved." << endl;
    if (!dinfo.isWritable())
        qDebug() << "Dir '" << dinfo.filePath().toAscii().constData() << "' is read-only. Metadata not saved." << endl;
    try {
        Exiv2::AccessMode mode;
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (QFile::encodeName(fullpath)));
        image->readMetadata();
        mode = image->checkMode(Exiv2::mdExif);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
            image->setExifData(exif_data);
        mode = image->checkMode(Exiv2::mdIptc);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
            image->setIptcData(iptc_data);
        // NOTE: Don't touch access and modification timestamp of file.
        struct stat st;
        ::stat(QFile::encodeName(fullpath), &st);

        struct utimbuf ut;
        ut.modtime = st.st_mtime;
        ut.actime  = st.st_atime;

        image->writeMetadata();

        ::utime(QFile::encodeName(fullpath), &ut);
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot save metadata using Exiv2 " << e.what() << endl;
    }

}

QString Photo::get_caption() const
{
	return caption;
}

void Photo::set_caption(const QString &caption_text)
{
    caption = caption_text;
    setIptcTagString("Iptc.Application2.Caption", caption_text);
    save_needed = true;
}

std::string Photo::get_thumbnail()
{
	return tn_data;
}

bool Photo::has_thumbnail()
{
	return (tn_len > 0 ? true : false);
}

bool Photo::has_georeference()
{
        //return has_georef;
    bool retval = false;
    double lat;
    double lon;
    if (this->getGPSLatitudeNumber(&lat) && this->getGPSLongitudeNumber(&lon))
        retval = true;
    return retval;
}

QByteArray Photo::getExifTagData(const char* exifTagName) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            char *s = new char[(*it).size()];
#if (EXIV2_TEST_VERSION(0,17,91))
            (*it).copy((Exiv2::byte*)s, Exiv2::bigEndian);
#else
            (*it).copy((Exiv2::byte*)s, exifData.byteOrder());
#endif
            QByteArray data(s, (*it).size());
            delete s;
            return data;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find Exif key " << exifTagName << " into image using Exiv2 " << endl;
    }

    return QByteArray();
}

bool Photo::getGPSLongitudeNumber(double *longitude) const
{
    try
    {
        double num, den, min, sec;
        *longitude=0.0;

        // Get the reference first.

        QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
        if (lngRef.isEmpty()) return false;

        // Longitude decoding.

        Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey2);
        if (it != exifData.end() && (*it).count() == 3)
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            if (den == 0)
                return false;
            *longitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            if (den == 0)
                return false;
            min = num/den;
            if (min != -1.0)
                *longitude = *longitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            if (den == 0)
                return false;
            sec = num/den;
            if (sec != -1.0)
                *longitude = *longitude + sec/3600.0;
        }
        else
            return false;

        if (lngRef[0] == 'W')
            *longitude *= -1.0;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot get Exif GPS tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

bool Photo::getGPSLatitudeNumber(double *latitude) const
{
    try
    {
        double num, den, min, sec;
        *latitude=0.0;

        // Get the reference first.

        QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
        if (latRef.isEmpty()) return false;

        // Latitude decoding.

        Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
        Exiv2::ExifData exifData(exif_data);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end() && (*it).count() == 3)
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            if (den == 0)
                return false;
            *latitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            if (den == 0)
                return false;
            min = num/den;
            if (min != -1.0)
                *latitude = *latitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            if (den == 0)
                return false;
            sec = num/den;
            if (sec != -1.0)
                *latitude = *latitude + sec/3600.0;
        }
        else
            return false;

        if (latRef[0] == 'S')
            *latitude *= -1.0;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot get Exif GPS tag using Exiv2 " << e.what() << endl;
    }

    return false;
}

QStringList Photo::get_keywords() const
{
    try
    {
        if (!iptc_data.empty())
        {
            QTextCodec::setCodecForCStrings(QTextCodec::codecForName("latin2"));
            QStringList keywords;
            Exiv2::IptcData iptcData(iptc_data);

            for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
            {
                QString key = QString::fromLocal8Bit(it->key().c_str());

                if (key == QString("Iptc.Application2.Keywords"))
                {
                    QString val(it->toString().c_str());
                    keywords.append(val);
                }
            }

            return keywords;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot get Iptc Keywords from image using Exiv2 " << e.what() << endl;
    }

    return QStringList();
}

QString Photo::getIptcTagString(const char* iptcTagName, bool escapeCR) const
{
    try
    {
    	QTextCodec::setCodecForCStrings(QTextCodec::codecForName("latin2"));
        Exiv2::IptcKey iptcKey(iptcTagName);
        Exiv2::IptcData iptcData(iptc_data);
        Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
        if (it != iptcData.end())
        {
            std::ostringstream os;
            os << *it;
            QString tagValue = QString::fromAscii(os.str().c_str());

            if (escapeCR)
                tagValue.replace("\n", " ");

            return tagValue;
        }
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot find Iptc key " << iptcTagName << " into image using Exiv2 " << e.what() << endl;
    }

    return QString();
}

bool Photo::setIptcTagString(const char *iptcTagName, const QString& value)
{
    try
    {
        QTextCodec::setCodecForCStrings(QTextCodec::codecForName("latin2"));
        iptc_data[iptcTagName] = std::string(value.toAscii().constData());
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Iptc tag string into image using Exiv2 " << e.what() << endl;
    }

    return false;
}


bool Photo::set_keywords(const QStringList& oldKeywords, const QStringList& newKeywords)
{
    try
    {
        QStringList oldkeys = oldKeywords;
        QStringList newkeys = newKeywords;
        // Remove all old keywords.
        Exiv2::IptcData iptcData(iptc_data);
        Exiv2::IptcData::iterator it = iptcData.begin();

    	QTextCodec::setCodecForCStrings(QTextCodec::codecForName("latin2"));
        while(it != iptcData.end())
        {
            QString key = QString::fromLocal8Bit(it->key().c_str());
            QString val(it->toString().c_str());

            // Also remove new keywords to avoid duplicates. They will be added again below.
            if ( key == QString("Iptc.Application2.Keywords") &&
                 (oldKeywords.contains(val) || newKeywords.contains(val)) )
                it = iptcData.erase(it);
            else
                ++it;
        };
        // Add new keywords. Note that Keywords Iptc tag is limited to 64 char
        Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords");

        for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
        {
            QString key = *it;
            key.truncate(64);

            Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
            val->read(key.toAscii().constData());
            iptcData.add(iptcTag, val.get());
        }
	iptc_data = iptcData;
        save_needed = true;
        return true;
    }
    catch( Exiv2::Error &e )
    {
        qDebug() << "Cannot set Iptc Keywords into image using Exiv2 " << e.what() << endl;
    }

    return false;
}

QString Photo::get_fullpath() const
{
    return fullpath;
}
